import 'package:intl/intl.dart';
import 'package:logger/logger.dart' as pkg;

class LogPrinter extends pkg.LogPrinter {
  LogPrinter({
    this.stackTraceBeginIndex = 0,
    this.methodCount = 2,
    this.errorMethodCount = 8,
    this.lineLength = 120,
    this.colors = true,
  });

  static final levelColors = {
    pkg.Level.verbose: pkg.AnsiColor.fg(pkg.AnsiColor.grey(0.5)),
    pkg.Level.debug: pkg.AnsiColor.none(),
    pkg.Level.info: pkg.AnsiColor.fg(81),
    pkg.Level.warning: pkg.AnsiColor.fg(135),
    pkg.Level.error: pkg.AnsiColor.fg(196),
    pkg.Level.wtf: pkg.AnsiColor.fg(199),
  };

  /// Matches a stacktrace line as generated on Android/iOS devices.
  /// For example:
  /// #1      Logger.log (package:logger/src/logger.dart:115:29)
  static final _deviceStackTraceRegex = RegExp(r'#[0-9]+[\s]+(.+) \(([^\s]+)\)');

  /// Matches a stacktrace line as generated by Flutter web.
  /// For example:
  /// packages/logger/src/printers/pretty_printer.dart 91:37
  static final _webStackTraceRegex = RegExp(r'^((packages|dart-sdk)/[^\s]+/)');

  /// Matches a stacktrace line as generated by browser Dart.
  /// For example:
  /// dart:sdk_internal
  /// package:logger/src/logger.dart
  static final _browserStackTraceRegex = RegExp(r'^(?:package:)?(dart:[^\s]+|[^\s]+)');

  final int stackTraceBeginIndex;
  final int methodCount;
  final int errorMethodCount;
  final int lineLength;
  final bool colors;

  @override
  List<String> log(pkg.LogEvent event) {
    final messageStr = event.message as String? ?? '';

    String? stackTraceStr;
    if (event.stackTrace == null) {
      if (methodCount > 0) {
        stackTraceStr = formatStackTrace(StackTrace.current, methodCount);
      }
    } else if (errorMethodCount > 0) {
      stackTraceStr = formatStackTrace(event.stackTrace, errorMethodCount);
    }

    final errorStr = event.error?.toString();

    final timeStr = getTime();

    return _formatAndPrint(
      event.level,
      messageStr,
      timeStr,
      errorStr,
      stackTraceStr,
    );
  }

  String? formatStackTrace(StackTrace? stackTrace, int methodCount) {
    var lines = stackTrace.toString().split('\n');
    if (stackTraceBeginIndex > 0 && stackTraceBeginIndex < lines.length - 1) {
      lines = lines.sublist(stackTraceBeginIndex);
    }
    final formatted = <String>[];
    var count = 0;
    for (final line in lines) {
      if (_discardDeviceStacktraceLine(line) ||
          _discardWebStacktraceLine(line) ||
          _discardBrowserStacktraceLine(line) ||
          line.isEmpty) {
        continue;
      }
      formatted.add('#$count   ${line.replaceFirst(RegExp(r'#\d+\s+'), '')}');
      if (++count == methodCount) {
        break;
      }
    }

    if (formatted.isEmpty) {
      return null;
    } else {
      return formatted.join('\n');
    }
  }

  bool _discardDeviceStacktraceLine(String line) {
    final match = _deviceStackTraceRegex.matchAsPrefix(line);
    if (match == null) {
      return false;
    }
    return match.group(2)!.startsWith('package:logger');
  }

  bool _discardWebStacktraceLine(String line) {
    final match = _webStackTraceRegex.matchAsPrefix(line);
    if (match == null) {
      return false;
    }
    return match.group(1)!.startsWith('packages/logger') ||
        match.group(1)!.startsWith('dart-sdk/lib');
  }

  bool _discardBrowserStacktraceLine(String line) {
    final match = _browserStackTraceRegex.matchAsPrefix(line);
    if (match == null) {
      return false;
    }
    return match.group(1)!.startsWith('package:logger') || match.group(1)!.startsWith('dart:');
  }

  String getTime() {
    final time = DateTime.now().toUtc();
    return DateFormat('dd.MM.yyyy HH:mm:ss').format(time);
  }

  // Handles any object that is causing JsonEncoder() problems
  Object toEncodableFallback(dynamic object) {
    return object.toString();
  }

  pkg.AnsiColor _getLevelColor(pkg.Level level) {
    if (colors) {
      return levelColors[level]!;
    } else {
      return pkg.AnsiColor.none();
    }
  }

  List<String> _formatAndPrint(
    pkg.Level level,
    String message,
    String timeString,
    String? error,
    String? stacktrace,
  ) {
    final buffer = <String>[];
    final color = _getLevelColor(level);

    final timePrefix = '$timeString |';

    if (message.isNotEmpty) {
      buffer.add('$timePrefix ${color(message)}');
    }

    if (error != null) {
      final lines = error.split('\n');

      for (final line in lines) {
        buffer.add('$timePrefix ${color(line)}');
      }
    }

    if (stacktrace != null) {
      for (final line in stacktrace.split('\n')) {
        buffer.add('$timePrefix ${color(line)}');
      }
    }

    return buffer;
  }
}
